﻿using System;
using System.IO;
using MLSharp.Utilities;

namespace MLSharp.RProject
{
	/// <summary>
	/// Converts a <see cref="IDataSet"/> to a table file
	/// that can be read by R.
	/// </summary>
	/// <remarks>All values are written as floating-point values.  This seems
	/// to work better for the AMORE package.</remarks>
	internal static class RTableWriter
	{
		#region Private Methods

		/// <summary>
		/// Converts the data set to an input table and writes it to the specified file.
		/// </summary>
		/// <param name="dataSet">The data set.</param>
		/// <param name="tableFilename">The name of the table file.</param>
		private static void WriteInputTable(IDataSet dataSet, string tableFilename)
		{
			using (StreamWriter writer = File.CreateText(tableFilename))
			{
				foreach (Instance instance in dataSet.Instances)
				{
					for (int i = 0; i < dataSet.Attributes.Count; i++)
					{
						//Don't write the target attribute.
						if (i == dataSet.TargetAttributeIndex)
							continue;

						double value = GetRealValue(dataSet.Attributes[i], instance.Values[i]);

						writer.Write("{0}\t", value.FormatForWriting());
					}

					writer.WriteLine();
				}
			}
		}

		/// <summary>
		/// Helper method that gets a double value to represent a value from an instance.
		/// This is needed since some values just represent indexes into a set of possible
		/// values.
		/// </summary>
		/// <param name="attribute">The attribute.</param>
		/// <param name="value">The raw value for that attribute.</param>
		/// <returns>
		/// If the attribute is numeric, the raw value is returned, otherwise, it is a set
		/// attribute, and the normalized index is returned (binary attributes are converted
		/// to -1,1, other attributes are left as-is).
		/// </returns>
		/// <exception cref="InvalidOperationException">Thrown if the value is NaN.</exception>
		private static double GetRealValue(DataAttribute attribute, double value)
		{
			if (double.IsNaN(value))
			{
				throw new InvalidOperationException("Missing value found, instances with missing values are not supported yet.");
			}

			//If it's binary, write it as -1 or 1, otherwise just write the index.
			if (attribute.Type == AttributeType.Set &&
			    attribute.PossibleValues != null &&
			    attribute.PossibleValues.Length == 2)
			{
				return value == 0 ? -1 : 1;
			}
			//Otherwise, just return the raw value.
			else
			{
				return value;
			}
		}

		#endregion

		#region Public Methods

		/// <summary>
		/// Converts the specified data set into a pair of table files (one for 
		/// inputs, one for outputs).
		/// </summary>
		/// <param name="dataSet">The data set.</param>
		/// <param name="inputsFilename">The name of the inputs file to create.</param>
		/// <param name="targetsFilename">The name of the targets file to create.</param>
		/// <remarks>
		/// If the files already exist, they are overwritten.
		/// </remarks>
		public static void Write(IDataSet dataSet, string inputsFilename, string targetsFilename)
		{
			//First, write the input variables.
			WriteInputTable(dataSet, inputsFilename);

			//Now write the target variables
			using (StreamWriter writer = File.CreateText(targetsFilename))
			{
				foreach (Instance instance in dataSet.Instances)
				{
					double value = GetRealValue(dataSet.Attributes[dataSet.TargetAttributeIndex],
					                            instance.Values[dataSet.TargetAttributeIndex]);

					writer.Write("{0}\t", value.FormatForWriting());

					writer.WriteLine();
				}
			}
		}

		/// <summary>
		/// Converts the inputs of data set to an table and writes it to the specified file.
		/// </summary>
		/// <param name="dataSet">The data set.</param>
		/// <param name="tableFilename">The name of the table file.</param>
		public static void Write(IDataSet dataSet, string tableFilename)
		{
			WriteInputTable(dataSet, tableFilename);
		}

		#endregion
	}
}
